﻿using System;

namespace Eg.Core
{

    public abstract class Entity<TId>
    {

        public virtual TId Id { get; protected set; }
        protected virtual int Version { get; set; }


        private static bool IsTransient(Entity<TId> obj)
        {
            return (obj != null) 
                // entity is transient if it has a default Id value (null, 0, Guid.Empty)
                && Equals(obj.Id, default(TId));
        }

        private Type GetUnproxiedType()
        {
            return GetType();
        }

        public override int GetHashCode()
        {
            if (Equals(Id, default(TId)))
                return base.GetHashCode();
            return Id.GetHashCode();
        }

        public virtual bool Equals(Entity<TId> other)
        {
            // x.Equals(null) must always return false
            if (other == null)
                return false;

            // if two variables reference the same instance, they are equal
            if (ReferenceEquals(this, other))
                return true;

            // If one or both entities are transient they are not equal
            if (IsTransient(this) || IsTransient(other))
            {
                return false;
            }

            // ok, both entities are persisted, so they both have POIDs
            if (!Equals(Id, other.Id))
            {
                return false;
            }

            // ok, both entities are persistent, and they have the same Id

            // but it's perfectly legal for an ActorRole entity to have the same POID as a Product entity
            // must compare the types
            // one of two can be a proxy - we must compare unproxied types !

            var otherType = other.GetUnproxiedType();
            var thisType = GetUnproxiedType();
            return thisType.IsAssignableFrom(otherType) || otherType.IsAssignableFrom(thisType);
        }

        public override bool Equals(object obj)
        {
            return Equals(obj as Entity<TId>); // If it can't be cast, null is passed instead, then false is returned
        }

    }

}
